Crate expect_test
source ·Expand description
Minimalistic snapshot testing for Rust.
§Introduction
expect_test
is a small addition over plain assert_eq!
testing approach,
which allows to automatically update tests results.
The core of the library is the expect!
macro. It can be though of as a
super-charged string literal, which can update itself.
Let’s see an example:
use expect_test::expect;
let actual = 2 + 2;
let expected = expect!["5"]; // or expect![["5"]]
expected.assert_eq(&actual.to_string())
Running this code will produce a test failure, as "5"
is indeed not equal
to "4"
. Running the test with UPDATE_EXPECT=1
env variable however would
“magically” update the code to:
let actual = 2 + 2;
let expected = expect!["4"];
expected.assert_eq(&actual.to_string())
This becomes very useful when you have a lot of tests with verbose and potentially changing expected output.
Under the hood, the expect!
macro uses file!
, line!
and column!
to
record source position at compile time. At runtime, this position is used
to patch the file in-place, if UPDATE_EXPECT
is set.
§Guide
expect!
returns an instance of Expect
struct, which holds position
information and a string literal. Use Expect::assert_eq
for string
comparison. Use Expect::assert_debug_eq
for verbose debug comparison. Note
that leading indentation is automatically removed.
use expect_test::expect;
#[derive(Debug)]
struct Foo {
value: i32,
}
let actual = Foo { value: 92 };
let expected = expect![["
Foo {
value: 92,
}
"]];
expected.assert_debug_eq(&actual);
Be careful with assert_debug_eq
- in general, stability of the debug
representation is not guaranteed. However, even if it changes, you can
quickly update all the tests by running the test suite with UPDATE_EXPECT
environmental variable set.
If the expected data is too verbose to include inline, you can store it in
an external file using the expect_file!
macro:
use expect_test::expect_file;
let actual = 42;
let expected = expect_file!["./the-answer.txt"];
expected.assert_eq(&actual.to_string());
File path is relative to the current file.
§Suggested Workflows
I like to use data-driven tests with expect_test
. I usually define a
single driver function check
and then call it from individual tests:
use expect_test::{expect, Expect};
fn check(actual: i32, expect: Expect) {
let actual = actual.to_string();
expect.assert_eq(&actual);
}
#[test]
fn test_addition() {
check(90 + 2, expect![["92"]]);
}
#[test]
fn test_multiplication() {
check(46 * 2, expect![["92"]]);
}
Each test’s body is a single call to check
. All the variation in tests
comes from the input data.
When writing a new test, I usually copy-paste an old one, leave the expect
blank and use UPDATE_EXPECT
to fill the value for me:
#[test]
fn test_division() {
check(92 / 2, expect![[]])
}
See https://blog.janestreet.com/using-ascii-waveforms-to-test-hardware-designs/ for a cool example of snapshot testing in the wild!
§Alternatives
- insta - a more feature full snapshot testing library.
- k9 - a testing library which includes support for snapshot testing among other things.
§Maintenance status
The main customer of this library is rust-analyzer. The library is stable, it is planned to not release any major versions past 1.0.
§Minimal Supported Rust Version
This crate’s minimum supported rustc
version is 1.60.0
. MSRV is updated
conservatively, supporting roughly 10 minor versions of rustc
. MSRV bump
is not considered semver breaking, but will require at least minor version
bump.
Macros§
- Creates an instance of
Expect
from string literal: - Creates an instance of
ExpectFile
from relative or absolute path:
Structs§
- Self-updating string literal.
- Self-updating file.
- Position of original
expect!
in the source file.